{
  // 实现一个repeat函数
  // function repeat(func, times, wait) {
  //   // TODO
  // }
  // const repeatFunc = repeat(alert, 4, 3000);
  // 调用这个 repeatFunc('helloworld') 会alert4次helloworld，每次间隔3s

  // 法一
  let log = console.log;
  function repeat(func, times, wait) {
    if (typeof func !== 'function') return;
    if (times <= 0) return;

    return function (value) {
      let timesTemp = times;
      let timer = setInterval(() => {
        func(value);
        timesTemp--;
        timesTemp === 0 && clearInterval(timer);
      }, wait);
    };
  }

  // 法二
  function repeat(func, times, wait) {
    if (typeof func !== 'function') return;
    if (times <= 0) return;

    return function (...args) {
      let i = 0;
      let _args = [...args];

      let timer = setInterval(() => {
        i++;
        if (i > times) {
          clearInterval(timer);
          return;
        }

        func.apply(null, _args);
      }, wait);
    };
  }
  const repeatFunc = repeat(log, 4, 3000);
  repeatFunc();
}

{
  // 按要求实现go函数
  // 实例：
  // go('l') // gol
  // go()('l') // gool
  // go()()()('l') // gooool

  // 考察闭包，原理上就是当没有传入参数时就默认是传‘o’然后累加起来返回函数本身，当最后有参数时就返回之前累计的参数拼接字符传返回。
  function go(...arg1) {
    let str = 'go';
    var addarg = function (...arg2) {
      if (!arg2[0]) {
        //当没有参数时候就累加默认的参数o 并返回当前函数保持对str的引用产生闭包
        str += 'o';
        return addarg;
      } else {
        //如果是有参数就直接返回最后累加的字符串
        return (str += arg2[0]);
      }
    };
    return addarg(...arg1);
  }
  // console.log(go("l")); // gol
  // console.log(go()()()()("l")); // goooool
}

{
  // 实现 (5).add(3).minus(2) 功能。
  // 链式调用 return this即可
  Number.prototype.add = function (num) {
    return this + num;
  };

  Number.prototype.minus = function (num) {
    return this - num;
  };

  // console.log((5).add(3).minus(2))
}

{
  // 某公司 1 到 12 月份的销售额存在一个对象里面
  // 如下：{1:222, 2:123, 5:888}，
  // 请把数据处理为如下结构：[222, 123, null, null, 888, null, null, null, null, null, null, null]。

  let obj = { 1: 222, 2: 123, 5: 888 };
  const res = new Array(12)
    .fill(null)
    .map((item, idx) => (obj[idx + 1] ? obj[idx + 1] : null));
  // console.log(res)
}

{
  // function getTags() {} 获取页面中所有标签，返回格式为['html','head','body','div']

  function getTags() {
    let doms = document.getElementsByTagName('*');
    console.log(doms);
    const result = [];
    const obj = {};
    for (let i = 0; i < doms.length; i++) {
      const tagName = doms[i].tagName.toLocaleLowerCase();
      if (!obj[tagName]) {
        result.push(tagName);
        obj[tagName] = tagName;
      }
    }

    return result;
  }

  // console.log(getTags())
}

{
  // 完成一个safeGet函数，可以按全的获取多层次的数据，，一旦数据不存在不会报错，返回undefined

  let obj = {
    a: {
      b: {
        c: 'yd',
      },
    },
  };

  function safeGet(o, path) {
    try {
      return path.split('.').reduce((lastRes, key) => lastRes[key], o);
    } catch (error) {
      return undefined;
    }
  }

  // console.log(safeGet(obj, 'a.b'))
  // console.log(safeGet(obj, 'a.b.c'))
  // console.log(safeGet(obj, 'a.b.c.d'))
  // console.log(safeGet(obj, 'a.b.c.d.f'))
}

{
  // lodash get方法
  function get(obj, path, defaultValue = undefined) {
    // 如果是数组访问，如a[0].b，需要用正则把[]转化成.运算符，然后将path转数组
    // a[0].b => a.0.b => [a, 0, b]
    const paths = Array.isArray(path) ? path : path.replace(/\[(\d+)\]/g, '.$1').split('.');
    let res = obj;

    for (const key of paths) {
      // 避免null和undefined读取属性报错，所以用Object包一下
      res = Object(res)[key];
      if (res === undefined) {
        return defaultValue
      }
    }

    return res;
  }
  console.log(get({ a: null }, "a.b.c", 3)); // output: 3
  console.log(get({ a: undefined }, "a", 3)); // output: 3
  console.log(get({ a: null }, "a.b", 3)); // output: 3
  console.log(get({ a: [{ b: 1 }] }, "a[0].b", 3)); // output: 1
  console.log(get({ a: { b: { c: 2 } } }, "a.b.c", 3)); // output: 2
}

{
  // [2,6,3,8,10,9]  返回一个数组，找到每个元素右侧第一个比它大的数字，没有的话返回-1   [6,8,8,10,-1,-1]
  function handleArr(arr) {
    let newArr = [];
    for (let index = 0; index < arr.length; index++) {
      if (arr[index] < arr[index + 1]) {
        newArr.push(arr[index + 1]);
      } else {
        newArr.push(-1);
      }
    }
    return newArr;
  }
  // console.log(handleArr([2,6,3,8,10,9]))
}

{
  // 请实现如下函数：
  // 可以批量请求数据，所有的url地址都在urls参数中，同时可以通过max参数控制请求的并发度，当所有请求结束后，需要执行callback回调函数。
  // 发请求的函数可以直接使用fetch即可

  /* 
    @param { array } urls 请求地址数组
    @param { Number } max最大并发请求数
    @param { Function } 回调
  */

  function parallelFetch(urls, max, callback) {
    if (!window.fetch || typeof window.fetch !== 'function') {
      throw Error('当前环境不支持fetch');
    }

    if (!urls || urls.length <= 0) {
      throw Error('请输入正确的请求地址');
    }

    // 请求地址数组的长度
    let _urlsLength = urls.length;
    // 保证最大并发数的有效性
    let _max = max || 1;
    // 当前可以正常请求的数量，保证最大并发数的安全性
    let _maxFetch = max < _urlsLength ? max : _urlsLength;
    // 当前完成请求的数量，用于判断何时调用回调
    let _finishedFetch = 0;
    // 当前请求地址的索引
    let _currentIndex = 0;

    console.log(`开始并发请求，接口总数为${_urlsLength}`);
    // 根据最大并发数进行循环请求，之后通过状态做递归请求
    for (let i = 0; i < _maxFetch; i++) {
      fetchFunc();
    }
    // 请求方法
    function fetchFunc() {
      // 所有请求都完成执行回调
      if (_finishedFetch === _urlsLength) {
        console.log(
          `当前一共${_urlsLength}个请求，完成个${_finishedFetch}请求`
        );
        if (typeof callback === 'function') return callback();
        return false;
      }

      // 如果当前请求地址的索引大于等于地址数组的长度是，则不在继续请求
      if (_currentIndex >= _urlsLength) {
        _maxFetch = 0;
      }

      // 如果可请求的数量大于0，则继续发起请求
      if (_maxFetch > 0) {
        console.log(
          `正在发起第${_currentIndex + 1
          }次请求，当前一共${_urlsLength}个请求，完成${_finishedFetch}个请求，当前请求地址是${urls[_currentIndex]
          }`
        );
        // 发起fetch请求
        fetch(urls[_currentIndex])
          .then((res) => {
            // TODO 正常业务逻辑
            // 请求结束，正常请求的数量+1
            _maxFetch++;
            _finishedFetch++;
            fetchFunc();
          })
          .catch((err) => {
            // TODO 异常逻辑
            // 请求结束，正常请求的数量+1
            _maxFetch++;
            _finishedFetch++;
            fetchFunc();
          });
        // 每次请求，当前请求地址的索引 + 1
        _currentIndex++;
        // 每次请求，可以正常请求的数量 - 1
        _maxFetch--;
      }
    }
  }

  let urls = [];
  for (let i = 0; i < 100; i++) {
    urls.push(`https://jsonplaceholder.typecode.com/todos/${i}`);
  }
  let max = 10;
  const callback = () => console.log('请求完了');

  parallelFetch(urls, max, callback);
}

{
  // 实现一个函数，给定一个urls数组，请求如何知道成功几个失败几个
  // 法一 Promise.allSettled 可以实现

  function getResult(urls) {
    let successCount = 0;
    let failCount = 0;
    let count = 0;

    return new Promise((resolve, reject) => {
      urls.forEach((url, index) => {
        fetch(url)
          .then((res) => {
            successCount += 1;
            count += 1;
            if (count === urls.length) {
              resolve({ successCount, failCount });
            }
          })
          .catch((err) => {
            failCount += 1;
            count += 1;
          });
      });
    });
  }

  let urls = [];
  for (let index = 0; index < 10; index++) {
    urls.push(
      `https://www.fastmock.site/mock/f29153ea802b2006237d862f7a54b82b/base/test?version=${index}`
    );
  }

  console.time('fecth');
  getResult(urls).then((res) => {
    console.timeEnd('fecth');
    console.log(res);
  });
}

{
  // 实现一个方法，将中文数字转化为数字
  function transform(str) {
    const numChar = {
      零: 0,
      一: 1,
      二: 2,
      三: 3,
      四: 4,
      五: 5,
      六: 6,
      七: 7,
      八: 8,
      九: 9,
    };

    const levleChar = {
      十: 10,
      百: 100,
      千: 1000,
      万: 10000,
      亿: 100000000,
    };

    let sum = 0;
    let temp = 0;
    for (let i = 0; i < str.length; i++) {
      let char = str[i];
      if (char === '零') continue;
      if (char === '亿' || char === '万') {
        sum += temp * levleChar[char];
        temp = 0;
      } else {
        let next = str[i + 1];
        if (next && next !== '亿' && next !== '万') {
          temp += numChar[char] * levleChar[next];
          i++;
        } else {
          temp += numChar[char];
        }
      }
    }

    return sum + temp;
  }
  console.log(transform('一十二亿三千零九十六万三千八百九十七'));
}

{
  // 实现以下代码
  // function add() {
  //   // your code
  // }
  // function once() {
  //   // your code
  // }
  // function two() {
  //   // your code
  // }
  // console.log(add(once(two()))); // 3
  // console.log(add(two(once()))); // 3

  function add() {
    return arguments[0].reduce((a, b) => a + b);
  }

  function once() {
    if (arguments.length === 0) {
      return 1;
    } else {
      return [arguments[0], 1];
    }
  }

  function two() {
    if (arguments.length === 0) {
      return 2;
    } else {
      return [arguments[0], 2];
    }
  }
  console.log(add(once(two())));
  console.log(add(two(once())));
}

{
  // 写一个函数打乱数组顺序，传入一个数组，返回一个打乱的数组
  let arr = [0, 1, 2, 3, 4, 5];
  function disorder(arr) {
    return [...arr].sort(() => Math.random() - 0.5);
  }

  console.log(disorder(arr));

  let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
  function shuffle(arr) {
    let i = arr.length;
    while (i) {
      let j = Math.floor(Math.random() * i--);
      [arr[j], arr[i]] = [arr[i], arr[j]];
    }
    return arr;
  }
  console.log(shuffle(arr));
}

{
  // 实现一个打点计时器
  // 要求
  // 1 从start 到 end 每个100ms输出一个数字，每次数字自增1
  // 2 返回的对象中需要包含cancel方法，用于停止定时器
  // 3 第一个数字要求立即输出
  const count = (start, end) => {
    console.log(start);
    let timer = setInterval(() => {
      if (start < end) {
        console.log(start++);
      }
    }, 100);

    return {
      cancel: () => {
        clearInterval(timer);
      },
    };
  };

  const fn = count(1, 100);
  fn.cancel();
}

{
  // 按要求完成代码
  const timeout = (ms) => {
    new Promise((resolve, reject) => {
      setTimeout(resolve, ms);
    });
  };

  const ajax1 = () => {
    timeout(2000).then((res) => {
      console.log('1');
      return 1;
    });
  };

  const ajax2 = () => {
    timeout(1000).then((res) => {
      console.log('2');
      return 2;
    });
  };

  const ajax3 = () => {
    timeout(2000).then((res) => {
      console.log('3');
      return 3;
    });
  };

  const mergePromise = async (ajaxArray) => {
    // your code
    console.log('ajaxArray: ', ajaxArray);
    // your code

    // for of 循环异步函数
    // return (async () => {
    //   let result = [];
    //   for (const item of ajaxArray) {
    //     const res = await item();
    //     result.push(res);
    //   }
    //   return result;
    // })();

    // es6串行
    // return (async function () {
    //   let idx = 0
    //   let len = ajaxArray.length
    //   let result = []
    //   while(idx < len) {
    //     let data = await ajaxArray[idx]()
    //     result.push(data)
    //     idx++
    //   }
    //   return result
    // })()

    // 串行
    return new Promise((resolve, reject) => {
      let ret = [];
      let promise = ajaxArray
        .map((ajax) => () => ajax().then((res) => ret.push(res)))
        .reduce((lastRes, item) => {
          return () => {
            return lastRes().then(item);
          };
        });

      promise().then((res) => {
        resolve(ret);
      });
    });
  };

  // const mergePromise = ajaxArray => {
  //   const data = []
  //   // Promise.resolve 方法不带参数的时候，直接返回一个resolved状态的Promise对象
  //   let sequence = Promise.resolve();
  //   ajaxArray.forEach(item => {
  //     sequence = sequence.then(item).then(res => {
  //       data.push(res)
  //       return data
  //     })
  //   })
  //   return sequence
  // };

  mergePromise([ajax1, ajax2, ajax3]).then((res) => {
    console.log('done');
    console.log(res); // res 为 [1,2,3]
  });
  // 要求执行结果为 1, 2, 3, done ,[1,2,3]
}

{
  // 数组去重
  // 法一 Set
  function unit(arr) {
    return [...new Set(arr)];
  }

  // 法二 indexOf或者includes
  function unit(arr) {
    return arr.reduce((prev, curr) => {
      !prev.includes(curr) && prev.push(curr);
      return prev;
    }, []);
  }

  // 法三 双循环
  function unit(arr) {
    for (let i = 0; i < arr.length; i++) {
      for (let j = i + 1; j < arr.length; j++) {
        if (arr[i] === arr[j]) {
          arr.splice(j, 1);
          j--; // 修复指针
        }
      }
    }
    return arr;
  }
}

{
  // 数组扁平化
  // [1, [2, [3, [4, [5]]]]] => [1, 2, 3, 4, 5]
  let arr = [1, [2, [3, [4, [5]]]]];

  // 法一
  // 递归实现
  const flatten = (arr) => {
    return arr.reduce((lastRes, item) => {
      return Array.isArray(item)
        ? lastRes.concat(flatten(item))
        : lastRes.concat(item);
    }, []);
  };

  // 法二
  // es10 api flat()
  arr.flat(Infinity);

  // 法三
  // 正则
  const flatten1 = (arr) => {
    let str = JSON.stringify(arr);
    // str = str.replace(/[\[|\]]/g, '');
    str = str.replace(/[[|\]]/g, '');

    return str.split(',').map((item) => +item);
  };

  // 法四
  // toString()
  const flatten2 = (arr) =>
    arr
      .toString()
      .split(',')
      .map((item) => +item);

  // 法五
  // 运算展开符 ...
  const flatten3 = (arr) => {
    while (arr.some((item) => Array.isArray(item))) {
      arr = [].concat(...arr);
    }
    return arr;
  };
}

{
  // 滴滴二面
  let list = [
    {
      id: '1',
      children: [
        {
          id: '11',
          children: [
            {
              id: '111',
            },
            {
              id: '112',
            },
          ],
        },
      ],
    },
    {
      id: '2',
      children: [
        {
          id: '21',
          children: [
            {
              id: '211',
            },
            {
              id: '212',
            },
          ],
        },
      ],
    },
  ];
  // 返回[2,21,212]，即目标值的所有父级元素的id
  // let ans = treeSearch(list, '212')

  function treeSearch(data = [], id = '') {
    function get(result, data) {
      for (const item of data) {
        if (item.id === id) {
          result.push(item.id);
          return result;
        } else if (item.children && item.children.length) {
          let res = get([...result, item.id], item.children);
          if (res) {
            return res;
          }
        }
      }
    }
    return get([], data);
  }

  // console.log(treeSearch(list, '212'));
}

{
  // 滴滴二面
  // 红灯三秒亮一次，绿灯一秒亮一次，黄灯2秒亮一次；
  // 如何让三个灯按照这个规律不断交替重复亮灯？（用Promse实现）三个亮灯函数已经存在：
  // function green() {
  //   console.log("green");
  // }
  // function yellow() {
  //   console.log("yellow");
  // }
  // function red() {
  //   console.log("red");
  // }

  // 法一改造了原函数
  async function green() {
    await sleep(1000);
    console.log('green');
  }

  async function yellow() {
    await sleep(2000);
    console.log('yellow');
  }

  async function red() {
    await sleep(3000);
    console.log('red');
  }

  function sleep(ts) {
    return new Promise((resolve) => setTimeout(resolve, ts));
  }

  async function changeLight() {
    await red();
    await green();
    await yellow();
    await changeLight();
  }
  changeLight();

  // 法二不改造原函数
  // function green() {
  //   console.log("green");
  // }

  // function yellow() {
  //   console.log("yellow");
  // }

  // function red() {
  //   console.log("red");
  // }

  // function sleep(fn, ts) {
  //   return new Promise((resolve, reject) => {
  //     setTimeout(() => {
  //       fn()
  //       resolve()
  //     }, ts)
  //   })
  // }

  // async function changeLight() {
  //   await sleep(red, 3000)
  //   await sleep(green, 1000)
  //   await sleep(yellow, 2000)
  //   await changeLight()
  // }
  // changeLight()

  // 法三 使用promise.resolve()
  function green() {
    console.log('green');
  }

  function yellow() {
    console.log('yellow');
  }

  function red() {
    console.log('red');
  }

  let light = function (wait, cb) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        cb();
        resolve();
      }, wait);
    });
  };

  let step = function () {
    Promise.resolve()
      .then(() => {
        return light(3000, red);
      })
      .then(() => {
        return light(1000, green);
      })
      .then(() => {
        return light(2000, yellow);
      })
      .then(() => {
        step();
      });
  };
  step();
}

{
  // 滴滴二面
  // nextTick 原理，手动实现一下
  // Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate，如果执行环境不支持，则会采用 setTimeout(fn, 0) 代替。
  // 实际上，nextTick(callback)类似于Promise().resolve().then(callback)或者setTimeout(callback,0);
  // 所以如果要想在DOM更新后获取DOM信息，就需要在本次异步任务创建之后创建一个异步任务

  const callbacks = []; // 相当于一个事件池，存储所有回调函数
  let pendding = false; // pendding：标记当前是否正在执行回掉函数
  let timerFunc; // 用来触发执行回调函数

  function nextTick(cb, ctx) {
    // push进callback数组
    callbacks.push(() => {
      cb.call(ctx);
    });

    if (!pendding) {
      pendding = true;
      // 执行timerFunc方法
      timerFunc();
    }
  }

  if (typeof Promise !== 'undefined') {
    // 判断是否原生支持Promise
    const p = Promise.resolve();
    timerFunc = () => {
      // 如果原生支持Promise 用Promise执行flushCallbacks
      p.then(flushCallbacks);
    };
  } else if (typeof MutationObserver !== 'undefined') {
    // 判断是否原生支持MutationObserver
    // 如果原生支持MutationObserver 用MutationObserver执行flushCallbacks
    const observer = new MutationObserver(flushCallbacks);
    const textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
      characterData: true,
    });
    timerFunc = () => {
      counter = (counter + 1) % 2;
      textNode.data = String(counter);
    };
  } else if (typeof setImmediate !== 'undefined') {
    // 判断是否原生支持setImmediate
    timerFunc = () => {
      setImmediate(flushCallbacks);
    };
  } else {
    // 都不支持的情况下使用setTimeout 0
    timerFunc = () => {
      // 如果原生支持setImmediate  用setImmediate执行flushCallbacks
      setTimeout(flushCallbacks, 0);
    };
  }

  // flushCallbacks 最终执行nextTick 方法传进来的回调函数
  function flushCallbacks() {
    pendding = false;
    const copies = callbacks.slice(0);
    callbacks.length = 0;
    for (let i = 0; index < copies.length; i++) {
      copies[i]();
    }
  }
}

{
  // 输出以下代码结果，为什么？如果希望每隔一秒输出一个结果，应该如何改造？
  // 注意不得改动square方法
  const list = [1, 2, 3];
  const square = (num) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(num * num);
      }, 1000);
    });
  };

  // 1秒后依次输出1 4 9
  // function test() {
  //   list.forEach(async x => {
  //     let res = await square(x)
  //     console.log(res)
  //   })
  // }

  // 改造如下，封装异步遍历函数
  async function asyncForEach(arr, callback) {
    for (let i = 0; i < arr.length; i++) {
      await callback(arr[i], i, arr);
    }
  }
  function test() {
    asyncForEach(list, async (x) => {
      let res = await square(x);
      console.log('res: ', res);
    });
  }

  test();

  // 下面方法也可以实现，但是没有用异步遍历，失去了此题的意义
  // function test(data = []) {
  //   if (data.length > 0) {
  //     const num = data.shift();
  //     square(num).then((res) => {
  //       console.log(res);
  //       test(data)
  //     });
  //   }
  // }
  // test(list);
}

{
  // 实现一个normalize函数，能将输入特定的字符串转化成的特定的数据结构
  // 字符串仅由小写字母和[]组成，且字符没有多余的空格
  // 如 'abc' => {value: 'abc'}
  // 如 '[abc[bcd[def]]]' => {value: 'abc', children: {value: 'bcd', children: {value: 'def'}}}

  function normalize(str) {
    let res = {};
    let c;
    // 字符串转数组
    let arr = str.split(/[\[|\]]/).filter(Boolean);
    // 生成结构化数据
    arr.forEach((item, index) => {
      if (index === 0) {
        res.value = item;
        c = res;
      } else {
        c.children = c.children || {};
        c.children.value = item;
        c = c.children;
      }
    });
    return res;
  }

  function normalize(str) {
    let res = {};
    let arr = str.split(/[\[|\]]/).filter(Boolean);
    arr.reduce((lastRes, item, index, arr) => {
      lastRes.value = item;
      if (index !== arr.length - 1) {
        return (lastRes.children = {});
      }
    }, res);

    return res;
  }
}

{
  // 公司计划面试 2N 人。第 i 人飞往 A 市的费用为 costs[i][0]，飞往 B 市的费用为 costs[i][1]。
  // 返回将每个人都飞到某座城市的最低费用，要求每个城市都有 N 人抵达。
  // 输入：[[10, 20], [30, 200], [400, 50], [30, 20]]
  // 输出：110
  // 解释：
  // 第一个人去 A 市，费用为 10。
  // 第二个人去 A 市，费用为 30。
  // 第三个人去 B 市，费用为 50。
  // 第四个人去 B 市，费用为 20。
  // 最低总费用为 10 + 30 + 50 + 20 = 110，每个城市都有一半的人在面试。
  // 输入：[[259, 770], [448, 54], [926, 667], [184, 139], [840, 118], [577, 469]]
  // 预期：1706

  function getOptimalPlan(arr = []) {
    let cityA = [],
      cityB = [],
      totalA = 0,
      totalB = 0,
      len = arr.length;
    for (let i = 0; i < arr.length; i++) {
      const el = arr[i];
      if (el[0] < el[1]) {
        // 数组每一项的第一个数小于第二个数 飞往A
        cityA.push(el);
      } else {
        // 否则 飞往B
        cityB.push(el);
      }
    }
    totalA = cityA
      .map((el) => el[0])
      .reduce((lastRes, item) => lastRes + item, 0);
    totalB = cityB
      .map((el) => el[1])
      .reduce((lastRes, item) => lastRes + item, 0);

    if (cityA.length < len / 2) {
      // B地人多
      // 找到目前B地 两个费用差值较小的数组送往A
      let n = len / 2 - cityA.length; // 需要送往A地的人数
      let temp = cityB
        .map((el) => el[0] - el[1])
        .sort((a, b) => a - b)
        .splice(0, 2); // 得到差值数组 排序 取要送往A地的个数

      let total = temp.reduce((lastRes, item) => lastRes + item, 0);
      totalA += total;
      totalB -= total;
    } else {
      // A地人多
      // 找到目前A地 两个费用差值较小的数组送往B
      let n = len / 2 - cityB.length; // 需要送往B地的人数
      let temp = cityA
        .map((el) => el[0] - el[1])
        .sort((a, b) => a - b)
        .splice(0, 2); // 得到差值数组 排序 取要送往A地的个数

      let total = temp.reduce((lastRes, item) => lastRes + item, 0);
      totalA -= total;
      totalB += total;
    }

    return totalA + totalB;
  }
  console.log(
    getOptimalPlan([
      [10, 20],
      [30, 200],
      [400, 50],
      [30, 20],
    ])
  );
  console.log(
    getOptimalPlan([
      [259, 770],
      [448, 54],
      [926, 667],
      [184, 139],
      [840, 118],
      [577, 469],
    ])
  );
}

{
  // 小米二面 （抄头条）
  // 实现有并行限制的Promise调度器 保证同时运行的任务最多有两个
  // class Scheduler {
  //   add(promiseCreator) {

  //   }
  //   // ...
  // }

  // const timeout = (time) => new Promise(resolve => {
  //   setTimeout(resolve, time)
  // })

  // const scheduler = new Scheduler()
  // const addTask = (time, order) => {
  //   scheduler.add(() => timeout(time)).then(() => console.log(order))
  // }

  // addTask(1000, '1')
  // addTask(500, '2')
  // addTask(300, '3')
  // addTask(400, '4')
  // output: 2 3 1 4

  // 分析 整个执行流程
  // 1、其实1 2两个任务开始执行
  // 2、500ms时，2任务执行完毕，输出2，3任务开始执行
  // 3、800ms时，3任务之执行完毕，输出3，4任务开始执行
  // 4、10000ms时，1任务执行完毕，输出1，此时只剩下4任务
  // 5、1200ms时，4任务执行完毕，输出4

  class Scheduler {
    constructor() {
      this.queue = []; // 队列
      this.maxCounts = 2; // 最大并行数量
      this.runCounts = 0; // 当前执行的promise
    }
    // 往队列插入promiseCreator函数
    add(promiseCreator) {
      this.queue.push(promiseCreator);
    }
    // 使用队列先进先出的特性
    // 每次从队列中取出Promise Generator并执行，此函数执行完毕后应该递归调用requeus方法，做到执行下一个Promise Generator方法
    request() {
      if (
        !this.queue ||
        !this.queue.length ||
        this.runCounts >= this.maxCounts
      ) {
        return;
      }

      this.runCounts++;
      this.queue
        .shift()()
        .then((res) => {
          this.runCounts--;
          this.request();
        });
    }
    // 启动函数 将并行任务数promise函数串联起来
    taskStart() {
      for (let i = 0; i < this.maxCounts; i++) {
        this.request();
      }
    }

    start() {
      if (this.queue.length === 0) {
        return;
      }

      while (this.runCounts < this.maxCounts) {
        this.runCounts++;
        this.queue
          .shift()()
          .then(() => {
            // then 表示任务完成
            this.runCounts--; // 任务数量减少
            this.start(); //
          });
      }
    }
  }

  const timeout = (time) =>
    new Promise((resolve) => {
      setTimeout(resolve, time);
    });

  const scheduler = new Scheduler();
  const addTask = (time, order) => {
    scheduler.add(() => timeout(time).then(() => console.log(order)));
  };

  // 调用addTask方法 将函数放入队列
  addTask(1000, '1');
  addTask(500, '2');
  addTask(300, '3');
  addTask(400, '4');

  // 调用启动函数
  scheduler.taskStart();

  // class Scheduler {
  //   constructor() {
  //     this.task = [];
  //     this.maxCount = 2;
  //     this.runCount = 0;
  //   }
  //   add(promiseCreator) {
  //     this.task.push(promiseCreator);
  //   }
  //   start() {
  //     if (this.task.length === 0) return
  //     while (this.runCount < this.maxCount) {
  //       this.runCount++
  //       this.task.shift()().then(() => { // then表示任务完成
  //         this.runCount--; // 任务数减少
  //         this.start(); // 任务数减少就继续添加任务
  //       })
  //     }
  //   }
  // }
  // const timeout = time =>
  //   new Promise(resolve => {
  //     setTimeout(resolve, time);
  //   });

  // const scheduler = new Scheduler();

  // const addTask = (time, order) => {
  //   scheduler.add(() => timeout(time).then(() => console.log(order)));
  // };
  // addTask(1000, "1");
  // addTask(500, "2");
  // addTask(300, "3");
  // addTask(400, "4");
  // scheduler.start();
}

{
  // 写一个函数实现一下功能
  // currySum(1, 2, 3); // 6
  // currySum(1, 2)(3); // 6
  // currySum(1)(2, 3); // 6
  // currySum(1)(2)(3); // 6

  // 柯里化作用
  // 1、延迟计算（用闭包把传入参数保存起来，当传入的参数足够执行函数时，开始执行函数）
  // 2、动态创建函数（参数不够时返回接受剩下参数的函数）
  // 3、参数复用（每个参数都可以多次复用）

  // 方法1
  // const curry = (fn) => {
  //   // curry() 运行完成的返回值就是judge
  //   return (judge = (...args) => {
  //     return args.length === fn.length
  //       ? fn(...args)
  //       : (...arg) => judge(...args, ...arg);
  //   });
  // };

  // 方法2
  const curry = (fn, ...args) => {
    // 函数的参数个数可以直接通过函数的length属性来访问(fn.length)
    // ...args是 调用函数currySum的实参个数，可以简单地理解为函数的argumenst，
    // 但是要注意箭头函数没有argumenst，可以通过...args来表示
    if (fn.length === args.length) {
      // 传入的参数个等于原始函数fn的参数个数时，直接执行该函数
      return fn.call(fn, ...args);
      // return fn(...args)
    }

    // 传入的参数个数小于原始函数fn的参数个数时
    // 则继续对当前函数进行柯里化，返回一个接收所有参数的（当前参数和剩余参数）
    return (...rest) => curry(fn, ...args, ...rest);
  };

  // 使用curry来创建sum方法
  const sum = (a, b, c) => a + b + c;
  const currySum = curry(sum);
  // 执行结果
  // console.log(currySum(1, 2, 3)); // 6
  // console.log(currySum(1, 2)(3)); // 6
  // console.log(currySum(1)(2, 3)); // 6
  // console.log(currySum(1)(2)(3)); // 6
}

{
  // 实现无限叠加 add(1)(2)(3)(4)

  // 在add方法中设置一个_add方法和一个arr数组，_add方法用来判断参数，如果传入的参数依旧存在，那么arr数组中加入此参数，否则进行累加
  function add(...args1) {
    let arr = args1;
    let _add = (...args2) => {
      if (args2.length) {
        arr = arr.concat(args2);
        return _add;
      } else {
        return arr.reduce((curr, prev) => curr + prev, 0);
      }
    }
    return _add;
  }

  console.log(add(1)(2)(3)(4)());
}

{
  let entry = {
    a: {
      b: {
        c: {
          dd: 'abcdd',
        },
      },
      d: {
        xx: 'adxx',
      },
      e: 'ae',
    },
  };

  // 要求转换成如下对象
  let output = {
    'a.b.c.dd': 'abcdd',
    'a.d.xx': 'adxx',
    'a.e': 'ae',
  };

  function flatObj(obj, parentKey = '', result = {}) {
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        // 当前key和当前value叠加，作为下一次遍历得key使用
        let keyName = `${parentKey}${key}`;
        if (typeof obj[key] === 'object') {
          flatObj(obj[key], `${keyName}.`, result);
        } else {
          result[keyName] = obj[key];
        }
      }
    }
    return result;
  }

  // console.log(entry)
  // console.log(flatObj(entry))
}

{
  let entry = {
    'a.b.c.dd': 'abcdd',
    'a.d.xx': 'adxx',
    'a.e': 'ae',
  };

  // 要求转换成如下对象
  let output = {
    a: {
      b: {
        c: {
          dd: 'abcdd',
        },
      },
      d: {
        xx: 'adxx',
      },
      e: 'ae',
    },
  };

  // 遍历对象，如果键名称含有 . 将最后一个子键拿出来，构成对象，如 {'a.b.c.dd': 'abcdd'} 变为 {'a.b.c': { dd: 'abcdd' }} ,
  // 如果变换后的新父键名中仍还有点，递归进行以上操作即可。

  let obj = {};
  for (const key of Object.keys(entry)) {
    // console.log(key)
    key.split('.').reduce((lastRes, item, index, arr) => {
      // console.log(lastRes, item, index, arr)
      lastRes[item] = lastRes[item] || (!arr[index + 1] && entry[key]) || {};
      return lastRes[item];
    }, obj);
  }
  // console.log(obj)

  function handleObj(obj) {
    let result = {};
    for (const key in obj) {
      key.split('.').reduce((lastRes, item, index, arr) => {
        if (index === arr.length - 1) {
          // a.b.c.dd切割的数组最后一个赋值
          lastRes[item] = obj[key];
          return;
        }
        lastRes[item] = lastRes[item] || {};
        return lastRes[item];
      }, result);
    }
    return result;
  }
}

{
  // 实现一个find函数，并且find函数能够满足下列条件

  // title数据类型为string | null
  // userId为主键，数据类型为number

  // 原始数据
  const data = [
    { userId: 8, title: 'title1' },
    { userId: 11, title: 'other' },
    { userId: 15, title: null },
    { userId: 19, title: 'title2' },
  ];

  // 查找data中，符合条件的数据，并进行排序
  const result = find(data)
    .where({
      title: /\d$/,
    })
    .orderBy('userId', 'desc');

  // 输出
  // [
  //   { userId: 19, title: 'title2' },
  //   { userId: 8, title: 'title1' },
  // ];

  function find(origin) {
    return {
      data: origin,
      where: function (searchObj) {
        const keys = Reflect.ownKeys(searchObj);
        // const keys = Object.keys(searchObj);
        for (let i = 0; i < keys.length; i++) {
          this.data = this.data.filter((item) =>
            searchObj[keys[i]].test(item[keys[i]])
          );
        }

        return find(this.data);
      },
      orderBy: function (key, sorter) {
        return this.data.sort((a, b) => {
          return sorter === 'desc' ? b[key] - a[key] : a[key] - b[key];
        });
      },
    };
  }
}

{
  // 对象的深度比较
  // 已知有两个对象obj1和obj2，实现isEqual函数判断对象是否相等
  const obj1 = {
    a: 1,
    c: 3,
    b: {
      c: [1, 2],
    },
  };
  const obj2 = {
    c: 4,
    b: {
      c: [1, 2],
    },
    a: 1,
  };

  // isEqual函数，相等输出true，不相等输出false
  isEqual(obj1, obj2);

  function isEqual(A, B) {
    // 可以序列化一下,直接对比json串
    // return JSON.stringify(obj1) === JSON.stringify(obj2);

    const keysA = Object.keys(A);
    const keysB = Object.keys(B);

    // 健长不一样 不想等
    if (keysA.length !== keysB.length) return false;

    for (let i = 0; i < keysA.length; i++) {
      const key = keysA[i];

      // 类型不一样 不想等
      if (typeof A[key] !== typeof B[key]) return false;

      // 不是对象的时候直接判断值是否想等
      if (
        typeof A[key] !== 'object' &&
        typeof B[key] !== 'object' &&
        A[key] !== B[key]
      ) {
        return false;
      }

      // 数组判断
      if (Array.isArray(A[key]) && Array.isArray(B[key])) {
        if (!arrayEqual(A[key], B[key])) return false;
      }

      // 递归判断
      if (typeof A[key] === 'object' && typeof B[key] === 'object') {
        if (!isEqual(A[key], B[key])) return false;
      }
    }

    return true;
  }

  function arrayEqual(arr1, arr2) {
    if (arr1.length !== arr2.length) return false;

    for (let i = 0; i < arr1.length; i++) {
      if (arr1[i] !== arr2[i]) return false;
    }

    return true;
  }
}

{
  // 判断JS对象是否存在循环引用
  const obj = {
    a: 1,
    b: 2,
  };

  obj.c = obj;

  // isHasCircle函数， 存在环输出true，不存在的话输出false
  isHasCircle(obj);

  // 法一 利用js的json序列化
  function isHasCircle(obj) {
    // JSON.stringify方法会自动检测传入的对象是否存在循环应用;
    try {
      JSON.stringify(obj);
      return false;
    } catch (error) {
      return true;
    }
  }

  // 法二
  function isHasCircle(obj) {
    let hasCycle = false;
    const map = new Map();

    function loop(obj) {
      const keys = Object.keys(obj);

      keys.forEach((key) => {
        const value = obj[key];

        if (typeof value === 'object' && value !== null) {
          if (map.has(value)) {
            hasCycle = true;
            return;
          } else {
            map.set(value);
            loop(value);
          }
        }
      });
    }
    loop(obj);

    return hasCycle;
  }
}

{
  // 将有同样元素的数组进行合并
  // 例如：
  const arr = [
    ['a', 'b', 'c'],
    ['a', 'd'],
    ['d', 'e'],
    ['f', 'g'],
    ['h', 'g'],
    ['i'],
  ];
  // [
  //   // 运行后的返回结果是：
  //   ['a', 'b', 'c', 'd', 'e'],
  //   ['f', 'g', 'h'],
  //   ['i'],
  // ];

  function transform(arr) {
    let res = [];
    arr.map((el) => el.sort()).sort();
    let lastItem = arr.reduce((pre, cur) => {
      if (cur.some((el) => pre && pre.includes(el))) {
        pre = pre.concat(cur);
      } else {
        res.push(pre);
        pre = cur; // 指针前移
      }

      return [...new Set(pre)];
    });
    res.push(lastItem);
    return res;
  }

  function transform(arr) {
    for (let i = 0; i < arr.length; i++) {
      // 查找当前元素的后一个元素中是否存在包含当前元素数组中的任何一个元素
      if (arr[i + 1]?.some((o) => arr[i].includes(o))) {
        // 存在直接更新当前元素数组，并去重
        arr[i] = [...new Set([...arr[i], ...arr[i + 1]])];
        // 删除当前元素的后一个元素
        arr.splice(i + 1, 1);
        // 删除的同时指针后移
        i--;
        continue;
      }
    }
    return arr;
  }
}

{
  // 数组拆分
  function chunk(arr = [], size = 1) {
    let result = [];
    for (let i = 0; i < arr.length; i = i + size) {
      newArr.push(arr.slice(i, i + size));
    }
    return result;
  }

  function chunk(arr = [], size = 1) {
    if (!arr.length) return [];
    return arr.reduce(
      (pre, cur) => {
        // pre[pre.length - 1].length === size
        //   ? pre.push([cur])
        //   : pre[pre.length - 1].push(cur);
        // return pre;
        return (
          pre[pre.length - 1].length === size
            ? pre.push([cur])
            : pre[pre.length - 1].push(cur),
          pre
        );
      },
      [[]]
    );
  }
}
